package com.pig4cloud.pigx.strategy.util;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.okex.open.api.config.APIConfiguration;
import com.okex.open.api.customize.util.ApiOkxUtil;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.customize.contants.SystemConfigConstants;
import com.pig4cloud.pigx.common.customize.enums.CommonDictEnum;
import com.pig4cloud.pigx.common.customize.exception.ApiRequestException;
import com.pig4cloud.pigx.common.customize.exception.DuckweedException;
import com.pig4cloud.pigx.common.customize.exception.MqAckException;
import com.pig4cloud.pigx.common.customize.exception.MqAckLogException;
import com.pig4cloud.pigx.common.customize.exception.MqNotAckException;
import com.pig4cloud.pigx.common.customize.redis.constants.CommonRedisConstants;
import com.pig4cloud.pigx.common.customize.redis.constants.RedisKeyEnum;
import com.pig4cloud.pigx.common.customize.redis.utils.RedisUtils;
import com.pig4cloud.pigx.common.customize.security.utils.SecurityUtil;
import com.pig4cloud.pigx.common.customize.utils.BigDecimalUtils;
import com.pig4cloud.pigx.common.customize.utils.CommonUtil;
import com.pig4cloud.pigx.common.data.resolver.ParamResolver;
import com.pig4cloud.pigx.strategy.bo.StrategyTaskContractCacheBO;
import com.pig4cloud.pigx.strategy.bo.okx.SyncOrderBO;
import com.pig4cloud.pigx.strategy.constant.StrategyMqConstant;
import com.pig4cloud.pigx.strategy.dto.StrategyContractEmailCloseDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractEmailDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractEmailErrorDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractEmailErrorResolvedDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractEmailFuelWarningDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractEmailOpenDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractEmailPauseDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractEmailRestoreDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractErrorSettlementDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractPauseDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractRestoreDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyPositionContractCloseDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyPositionContractClosePartDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyPositionContractOpenDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyStopOrderContractAddDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyStopOrderContractCancelBatchDTO;
import com.pig4cloud.pigx.strategy.dto.StrategySyncOrderMsgDTO;
import com.pig4cloud.pigx.strategy.dto.StrategySyncStopOrderMsgDTO;
import com.pig4cloud.pigx.strategy.entity.StrategyContract;
import com.pig4cloud.pigx.strategy.entity.StrategyGroupContract;
import com.pig4cloud.pigx.strategy.entity.StrategyOrderContract;
import com.pig4cloud.pigx.strategy.entity.StrategyPositionContract;
import com.pig4cloud.pigx.strategy.entity.StrategyTaskContract;
import com.pig4cloud.pigx.strategy.enums.StrategyContractEnum;
import com.pig4cloud.pigx.strategy.enums.StrategyOrderContractEnum;
import com.pig4cloud.pigx.strategy.enums.StrategyPositionContractEnum;
import com.pig4cloud.pigx.strategy.enums.StrategyStopOrderContractEnum;
import com.pig4cloud.pigx.strategy.enums.StrategyTaskContractEnum;
import com.pig4cloud.pigx.strategy.mapper.StrategyPositionContractMapper;
import com.pig4cloud.pigx.strategy.mapper.StrategyTaskContractMapper;
import com.pig4cloud.pigx.strategy.mapstruct.StrategyTaskContractStruct;
import com.pig4cloud.pigx.system.api.feign.SystemProductFeign;
import com.pig4cloud.pigx.system.constant.SystemMqConstant;
import com.pig4cloud.pigx.system.constant.SystemParamNameConstant;
import com.pig4cloud.pigx.system.entity.SystemLog;
import com.pig4cloud.pigx.system.entity.SystemPlatform;
import com.pig4cloud.pigx.system.entity.SystemProduct;
import com.pig4cloud.pigx.user.api.feign.FuelAccountFeign;
import com.pig4cloud.pigx.user.constant.UserMqConstant;
import com.pig4cloud.pigx.user.constant.WalletConstant;
import com.pig4cloud.pigx.user.dto.TransactionThenDTO;
import com.pig4cloud.pigx.user.dto.UpdateFuelBalanceDTO;
import com.pig4cloud.pigx.user.entity.UserApi;
import com.pig4cloud.pigx.user.enums.FuelConsumeEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 策略-合约工具类
 *
 * @since 2022/11/27 17:49
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class StrategyContractUtil {

    private final StrategyTaskContractMapper strategyTaskContractMapper;
    private final StrategyPositionContractMapper strategyPositionContractMapper;
    private final FuelAccountFeign fuelAccountFeign;
    private final SystemProductFeign systemProductFeign;

    private final RedisUtils redisUtils;
    private final ApiOkxUtil apiOkxUtil;
    private final SecurityUtil securityUtil;
    private final CommonUtil commonUtil;
    private final AmqpTemplate rabbit;
    private final RedisTemplate redis;

    /**
     * 整理策略和产品的映射关系
     *
     * @param strategyList 策略集合
     * @param productList  产品集合
     * @return {@link Map< Long, SystemProduct>}
     * @since 2022/11/27 17:51
     */
    public static Map<Long, SystemProduct> getStrategyProductMapping(List<StrategyContract> strategyList, List<SystemProduct> productList) {

        Map<Long, SystemProduct> strategyProductMapping = new HashMap<>(strategyList.size());
        strategyList.forEach(strategy -> {
            for (SystemProduct product : productList) {
                if (Objects.equals(product.getId(), strategy.getProductId())) {
                    strategyProductMapping.put(strategy.getId(), product);
                    break;
                }
            }
        });

        return strategyProductMapping;

    }

    /**
     * 整理策略和产品的映射关系
     *
     * @param strategyList 策略集合
     * @param productIdSet 产品id集合
     * @return {@link Map< Long, SystemProduct>}
     * @since 2022/11/27 17:51
     */
    public Map<Long, SystemProduct> getStrategyProductMapping(List<StrategyContract> strategyList, Set<Long> productIdSet) {

        // 查询产品
        List<SystemProduct> productList = redisUtils.getPersistentCache(RedisKeyEnum.PRODUCT, productIdSet);
        if (CollectionUtils.isEmpty(productList)) {
            return new HashMap<>(0);
        }

        return getStrategyProductMapping(strategyList, productList);

    }

    /**
     * 整理策略和产品的映射关系
     *
     * @param strategyIdSet 策略id集合
     * @return {@link Map< Long, SystemProduct>}
     * @since 2022/11/27 17:51
     */
    public Map<Long, SystemProduct> getStrategyProductMapping(Set<Long> strategyIdSet) {

        // 查询策略
        List<StrategyContract> strategyList = redisUtils.getPersistentCache(RedisKeyEnum.STRATEGY_CONTRACT, strategyIdSet);
        if (CollectionUtils.isEmpty(strategyList)) {
            return new HashMap<>(0);
        }

        return getStrategyProductMapping(strategyList);

    }

    /**
     * 整理策略和产品的映射关系
     *
     * @param strategyList 策略集合
     * @return {@link Map< Long, SystemProduct>}
     * @since 2022/11/27 17:51
     */
    public Map<Long, SystemProduct> getStrategyProductMapping(List<StrategyContract> strategyList) {

        if (CollectionUtils.isEmpty(strategyList)) {
            return new HashMap<>(0);
        }

        // 归集产品id
        Set<Long> productIdSet = strategyList.stream()
                .map(StrategyContract::getProductId)
                .collect(Collectors.toSet());

        return getStrategyProductMapping(strategyList, productIdSet);

    }

    /**
     * 获取策略缓存
     *
     * @param id       id
     * @param nullable 是否可以为空
     * @return {@link StrategyContract}
     * @since 2022/11/28 10:46
     */
    public StrategyContract getStrategyCache(Long id, boolean nullable) {

        StrategyContract strategy = redisUtils.getPersistentCache(RedisKeyEnum.STRATEGY_CONTRACT, id, StrategyContract.class);
        if (Objects.isNull(strategy) && !nullable) {
            log.error("策略数据异常，id：{}", id);
            throw new MqAckLogException("策略数据异常");
        }

        return strategy;

    }

    /**
     * 获取策略组缓存
     *
     * @param id       id
     * @param nullable 是否可以为空
     * @return {@link StrategyGroupContract}
     * @since 2022/11/28 10:46
     */
    public StrategyGroupContract getStrategyGroupCache(Long id, boolean nullable) {

        StrategyGroupContract group = redisUtils.getPersistentCache(RedisKeyEnum.STRATEGY_GROUP_CONTRACT, id, StrategyGroupContract.class);
        if (Objects.isNull(group) && !nullable) {
            log.error("策略组数据异常，id：{}", id);
            throw new MqAckLogException("策略组数据异常");
        }

        return group;

    }

    /**
     * 获取任务缓存
     *
     * @param id       id
     * @param nullable 是否可以为空
     * @return {@link StrategyTaskContractCacheBO}
     * @since 2022/11/28 10:46
     */
    public StrategyTaskContractCacheBO getStrategyTaskCache(Long id, boolean nullable) {

        StrategyTaskContractCacheBO task = redisUtils.getPersistentCache(RedisKeyEnum.STRATEGY_TASK_CONTRACT, id, StrategyTaskContractCacheBO.class);
        if (Objects.isNull(task) && !nullable) {
            log.error("任务数据异常，id：{}", id);
            throw new MqAckLogException("任务数据异常");
        }

        return task;

    }

    /**
     * 获取平台缓存
     *
     * @param id       id
     * @param nullable 是否可以为空
     * @return {@link SystemPlatform}
     * @since 2022/11/28 10:46
     */
    public SystemPlatform getSystemPlatformCache(Long id, boolean nullable) {

        SystemPlatform platform = redisUtils.getPersistentCache(RedisKeyEnum.PLATFORM, id, SystemPlatform.class);
        if (Objects.isNull(platform) && !nullable) {
            log.error("平台数据异常，id：{}", id);
            throw new MqAckLogException("平台数据异常");
        }

        return platform;

    }

    /**
     * 获取产品缓存
     *
     * @param id       id
     * @param nullable 是否可以为空
     * @return {@link SystemProduct}
     * @since 2022/11/28 10:46
     */
    public SystemProduct getSystemProductCache(Long id, boolean nullable) {

        SystemProduct product = redisUtils.getPersistentCache(RedisKeyEnum.PRODUCT, id, SystemProduct.class);
        if (Objects.isNull(product) && !nullable) {
            log.error("产品数据异常，id：{}", id);
            throw new MqAckLogException("产品数据异常");
        }

        return product;

    }

    /**
     * 获取订单缓存
     *
     * @param id       id
     * @param nullable 是否可以为空
     * @return {@link StrategyOrderContract}
     * @since 2022/11/28 10:46
     */
    public StrategyOrderContract getStrategyOrderCache(Long id, boolean nullable) {

        StrategyOrderContract contract = redisUtils.getPersistentCache(RedisKeyEnum.STRATEGY_ORDER_CONTRACT, id, StrategyOrderContract.class);
        if (Objects.isNull(contract) && !nullable) {
            log.error("订单数据异常，id：{}", id);
            throw new MqAckException("订单数据异常");
        }

        return contract;

    }


    /**
     * 检查策略缓存
     *
     * @param strategy 策略
     * @since 2022/11/28 10:46
     */
    public static void checkStrategyCache(StrategyContract strategy) {

        if (Objects.isNull(strategy)) {
            throw new DuckweedException("策略已失效");
        }

        // 是否停用
        if (!Objects.equals(CommonDictEnum.Status.NORMAL.getValue(), strategy.getStatus())) {
            throw new DuckweedException("策略已停用");
        }

    }

    /**
     * 检查策略组缓存
     *
     * @param group 策略组
     * @since 2022/11/28 10:46
     */
    public static void checkStrategyGroupCache(StrategyGroupContract group) {

        if (Objects.isNull(group)) {
            throw new DuckweedException("策略组已失效");
        }

        // 是否停用
        if (!Objects.equals(CommonDictEnum.Status.NORMAL.getValue(), group.getStatus())) {
            throw new DuckweedException("策略组已停用");
        }

    }

    /**
     * 初始化任务
     *
     * @param platformId 平台id
     * @param strategyId 策略id
     * @param userId     用户id
     * @param username   用户名称
     * @param switchId   开关id
     * @return {@link StrategyTaskContract}
     * @since 2022/11/28 14:47
     */
    public StrategyTaskContract initTask(Long platformId, Long strategyId, Long userId, String username, Long switchId) {
        StrategyTaskContract task = new StrategyTaskContract();
        task.setPlatformId(platformId);
        task.setSwitchId(switchId);
        task.setStrategyId(strategyId);
        task.setUserId(userId);
        task.setFirstAddSide(StrategyTaskContractEnum.FirstAddSide.NONE.getValue());
        task.setAddCount(0);
        task.setOrderCount(0);
        task.setProfit(BigDecimal.ZERO);
        task.setNetProfit(BigDecimal.ZERO);
        task.setHandlingFee(BigDecimal.ZERO);
        task.setFuelFrozen(BigDecimal.ZERO);
        task.setFuelCost(BigDecimal.ZERO);
        task.setPendingOrderNum(0);
        task.setSyncStopOrderNum(0);
        task.setErrorSettlementPrice(BigDecimal.ZERO);
        task.setErrorSettlementProfit(BigDecimal.ZERO);
        task.setErrorSettlementStatus(StrategyTaskContractEnum.ErrorSettlementStatus.NONE.getValue());
        task.setForwardStopCloseNum(0);
        task.setForwardStopRollbackNum(0);
        task.setStatus(StrategyTaskContractEnum.Status.RUNNING.getValue());
        task.setCreateBy(userId);
        task.setCreateName(username);
        task.setUpdateBy(userId);
        task.setUpdateName(username);
        securityUtil.initCreateAbout(task);

        return task;
    }

    /**
     * 发送消息，运行任务
     *
     * @param id id
     * @since 2022/11/28 18:09
     */
    public void sendMqRunTask(String id) {
        // 发送mq，运行任务
        rabbit.convertAndSend(StrategyMqConstant.EXCHANGE_FANOUT_STRATEGY_TASK_CONTRACT_HANDLE, null, id);
    }

    /**
     * 发送消息，同步订单信息
     *
     * @param dto bo
     * @since 2022/11/28 18:09
     */
    public void sendMqSyncOrder(StrategySyncOrderMsgDTO dto) {
        // 发送mq，运行任务
        rabbit.convertAndSend(StrategyMqConstant.EXCHANGE_FANOUT_DELAY_STRATEGY_ORDER_CONTRACT_SYNC, null, dto);
    }

    /**
     * 发送消息，同步止盈止损
     *
     * @param dto bo
     * @since 2022/11/28 18:09
     */
    public void sendMqSyncStopOrder(StrategySyncStopOrderMsgDTO dto) {
        // 发送mq，运行任务
        rabbit.convertAndSend(StrategyMqConstant.EXCHANGE_FANOUT_DELAY_STRATEGY_STOP_ORDER_CONTRACT_SYNC, null, dto);
    }

    /**
     * 发送消息，自动创建新任务
     *
     * @param switchId 开关id
     * @since 2022/11/28 18:09
     */
    public void sendMqTaskCreate(Long switchId) {
        // 发送mq，自动创建新任务
        rabbit.convertAndSend(StrategyMqConstant.EXCHANGE_FANOUT_STRATEGY_TASK_CONTRACT_CREATE, null, switchId);
    }

    /**
     * 发送消息，制度分红
     *
     * @param dto bo
     * @since 2022/11/28 18:09
     */
    public void sendMqDividend(TransactionThenDTO dto) {
        // 发送mq，运行任务
        rabbit.convertAndSend(UserMqConstant.EXCHANGE_FANOUT_USER_FUEL_CHECK, null, dto);
    }

    /**
     * 发送消息，暂停策略
     *
     * @param dto dto
     * @since 2022/12/10 11:54
     */
    public void sendMqSwitchPause(StrategyContractPauseDTO dto) {
        rabbit.convertAndSend(StrategyMqConstant.EXCHANGE_FANOUT_STRATEGY_CONTRACT_PAUSE, null, dto);
    }

    /**
     * 发送消息，恢复策略
     *
     * @param dto dto
     * @since 2022/12/10 11:54
     */
    public void sendMqSwitchRestore(StrategyContractRestoreDTO dto) {
        rabbit.convertAndSend(StrategyMqConstant.EXCHANGE_FANOUT_STRATEGY_CONTRACT_RESTORE, null, dto);
    }

    /**
     * 发送消息，平仓
     *
     * @param dto dto
     * @since 2022/12/10 11:54
     */
    public void sendMqPositionClose(StrategyPositionContractCloseDTO dto) {
        rabbit.convertAndSend(StrategyMqConstant.EXCHANGE_FANOUT_STRATEGY_POSITION_CONTRACT_CLOSE, null, dto);
    }

    /**
     * 发送消息，燃油币余额告警检查
     *
     * @param userId 用户id
     * @since 2022/12/10 11:54
     */
    public void sendMqFuelWarning(Long userId) {
        rabbit.convertAndSend(StrategyMqConstant.EXCHANGE_FANOUT_STRATEGY_CONTRACT_FUEL_WARNING, null, userId);
    }

    /**
     * 发送消息，异常仓位结算
     *
     * @param dto dto
     * @since 2022/12/22 20:12
     */
    public void sendMqErrorSettlement(StrategyContractErrorSettlementDTO dto) {
        rabbit.convertAndSend(StrategyMqConstant.EXCHANGE_FANOUT_STRATEGY_TASK_CONTRACT_ERROR_SETTLEMENT, null, dto);
    }

    /**
     * 发送消息，系统日志
     *
     * @param systemLog 日志数据
     * @since 2022/12/10 11:54
     */
    public void sendMqSystemLog(SystemLog systemLog) {
        rabbit.convertAndSend(SystemMqConstant.EXCHANGE_FANOUT_SYSTEM_LOG, null, systemLog);
    }

    /**
     * 发送消息，创建止盈止损
     *
     * @param dto dto
     * @since 2022/12/10 11:54
     */
    public void sendMqStopOrderCreate(StrategyStopOrderContractAddDTO dto) {
        rabbit.convertAndSend(StrategyMqConstant.EXCHANGE_FANOUT_STRATEGY_STOP_ORDER_CONTRACT_CREATE, null, dto);
    }

    /**
     * 发送消息，撤销止盈止损
     *
     * @param dto dto
     * @since 2022/12/10 11:54
     */
    public void sendMqStopOrderCancel(StrategyStopOrderContractCancelBatchDTO dto) {
        rabbit.convertAndSend(StrategyMqConstant.EXCHANGE_FANOUT_STRATEGY_STOP_ORDER_CONTRACT_CANCEL, null, dto);
    }

    /**
     * 发送消息，手动开仓
     *
     * @param dto dto
     * @since 2022/12/10 11:54
     */
    public void sendMqPositionOpenManual(StrategyPositionContractOpenDTO dto) {
        rabbit.convertAndSend(StrategyMqConstant.EXCHANGE_FANOUT_STRATEGY_POSITION_CONTRACT_OPEN_MANUAL, null, dto);
    }

    /**
     * 发送消息，手动平仓
     *
     * @param dto dto
     * @since 2022/12/10 11:54
     */
    public void sendMqPositionCloseManual(StrategyPositionContractClosePartDTO dto) {
        rabbit.convertAndSend(StrategyMqConstant.EXCHANGE_FANOUT_STRATEGY_POSITION_CONTRACT_CLOSE_PART, null, dto);
    }

    /**
     * 发送消息，邮件
     *
     * @param dto dto
     * @since 2022/12/10 11:54
     */
    public void sendMqEmail(StrategyContractEmailDTO dto) {
        rabbit.convertAndSend(StrategyMqConstant.EXCHANGE_FANOUT_STRATEGY_EMAIL, null, dto);
    }

    /**
     * 发送策略开启邮件
     *
     * @param userId       用户id
     * @param strategyName 策略名称
     * @since 2022/12/11 17:10
     */
    public void sendEmailStrategyOpen(Long userId, String strategyName) {
        StrategyContractEmailOpenDTO dto = new StrategyContractEmailOpenDTO();
        dto.setStrategyName(strategyName);
        dto.setTime(LocalDateTime.now());
        dto.setUserId(userId);
        sendMqEmail(dto);
    }

    /**
     * 发送策略关闭邮件
     *
     * @param userId       用户id
     * @param strategyName 策略名称
     * @param reason       原因
     * @since 2022/12/11 17:10
     */
    public void sendEmailStrategyClose(Long userId, String strategyName, String reason) {
        StrategyContractEmailCloseDTO dto = new StrategyContractEmailCloseDTO();
        dto.setStrategyName(strategyName);
        dto.setTime(LocalDateTime.now());
        dto.setUserId(userId);
        dto.setReason(reason);
        sendMqEmail(dto);
    }

    /**
     * 发送策略暂停邮件
     *
     * @param userId   用户id
     * @param strategy 策略
     * @param group    策略组
     * @param reason   原因
     * @since 2022/12/11 17:10
     */
    public void sendEmailStrategyPause(Long userId, StrategyContract strategy, StrategyGroupContract group, String reason) {
        StrategyContractEmailPauseDTO dto = new StrategyContractEmailPauseDTO();
        dto.setStrategyName(StrategyContractUtil.getStrategyName(group, strategy));
        dto.setTime(LocalDateTime.now());
        dto.setUserId(userId);
        dto.setReason(reason);
        sendMqEmail(dto);
    }

    /**
     * 发送策略恢复邮件
     *
     * @param userId   用户id
     * @param strategy 策略
     * @param group    策略组
     * @param reason   原因
     * @since 2022/12/11 17:10
     */
    public void sendEmailStrategyRestore(Long userId, StrategyContract strategy, StrategyGroupContract group, String reason) {
        StrategyContractEmailRestoreDTO dto = new StrategyContractEmailRestoreDTO();
        dto.setStrategyName(StrategyContractUtil.getStrategyName(group, strategy));
        dto.setTime(LocalDateTime.now());
        dto.setUserId(userId);
        dto.setReason(reason);
        sendMqEmail(dto);
    }

    /**
     * 发送策略异常邮件
     *
     * @param userId   用户id
     * @param strategy 策略
     * @param group    策略组
     * @param reason   原因
     * @since 2022/12/11 17:10
     */
    public void sendEmailStrategyError(Long userId, StrategyContract strategy, StrategyGroupContract group, String reason) {
        StrategyContractEmailErrorDTO dto = new StrategyContractEmailErrorDTO();
        dto.setStrategyName(StrategyContractUtil.getStrategyName(group, strategy));
        dto.setTime(LocalDateTime.now());
        dto.setUserId(userId);
        dto.setReason(reason);
        sendMqEmail(dto);
    }

    /**
     * 发送策略异常解决邮件
     *
     * @param userId   用户id
     * @param strategy 策略
     * @param group    策略组
     * @since 2022/12/11 17:10
     */
    public void sendEmailStrategyErrorResolved(Long userId, StrategyContract strategy, StrategyGroupContract group) {
        StrategyContractEmailErrorResolvedDTO dto = new StrategyContractEmailErrorResolvedDTO();
        dto.setStrategyName(StrategyContractUtil.getStrategyName(group, strategy));
        dto.setTime(LocalDateTime.now());
        dto.setUserId(userId);
        sendMqEmail(dto);
    }

    /**
     * 发送策略燃料币不足告警邮件
     *
     * @param userId           用户id
     * @param strategy         策略
     * @param group            策略组
     * @param fuelBalance      燃油币账户余额
     * @param fuelWarningRatio 燃料币预警值比例
     * @param fuelFrozen       预扣燃油费
     * @since 2022/12/11 21:30
     */
    public void sendEmailStrategyFuelWarning(Long userId,
                                             StrategyContract strategy,
                                             StrategyGroupContract group,
                                             BigDecimal fuelBalance,
                                             BigDecimal fuelWarningRatio,
                                             BigDecimal fuelFrozen) {
        StrategyContractEmailFuelWarningDTO dto = new StrategyContractEmailFuelWarningDTO();
        dto.setFuelBalance(fuelBalance);
        dto.setLowFuelRatio(fuelWarningRatio);
        dto.setFuelFrozen(fuelFrozen);
        dto.setStrategyName(StrategyContractUtil.getStrategyName(group, strategy));
        dto.setTime(LocalDateTime.now());
        dto.setUserId(userId);
        sendMqEmail(dto);
    }

    /**
     * 是否是做多
     *
     * @param side 方向（0做多、1做空）
     * @return {@link boolean}
     * @since 2022/11/29 14:28
     */
    public static boolean isLong(String side) {

        if (Strings.isBlank(side)) {
            throw new MqAckLogException("side为空");
        }

        return Objects.equals(StrategyContractEnum.Side.LONG.getValue(), side);

    }

    /**
     * 是否是做空
     *
     * @param side 方向（0做多、1做空）
     * @return {@link boolean}
     * @since 2022/11/29 14:28
     */
    public static boolean isShort(String side) {

        if (Strings.isBlank(side)) {
            throw new MqAckLogException("side为空");
        }

        return Objects.equals(StrategyContractEnum.Side.SHORT.getValue(), side);

    }

    /**
     * 获得方向对应的数值，用以在计算收益等需要加入方向作为参数的公式中使用
     *
     * @param side 方向（0做多、1做空）
     * @return {@link BigDecimal}
     * @since 2022/11/30 20:53
     */
    public static BigDecimal getSideValue(String side) {
        return isLong(side) ? BigDecimal.ONE : BigDecimal.ONE.negate();
    }

    /**
     * 获得另一个方向
     *
     * @param side 方向（0做多、1做空）
     * @return {@link String}
     * @since 2022/11/30 20:53
     */
    public static String getAnotherSide(String side) {
        return isLong(side) ? StrategyContractEnum.Side.SHORT.getValue() : StrategyContractEnum.Side.LONG.getValue();
    }

    /**
     * 获取手续费率
     *
     * @return {@link BigDecimal}
     * @since 2022/12/2 10:01
     */
    public static BigDecimal getHandlingFeeRate() {
        String handlingFeeRateStr = ParamResolver.getStr(SystemParamNameConstant.ORDER_CONTRACT_HANDLING_FEE_RATE, "");
        // System.err.println("手续费率：" + handlingFeeRateStr);
        if (Strings.isBlank(handlingFeeRateStr)) {
            return BigDecimal.ZERO;
        }

        return BigDecimalUtils.str2BigDecimal(handlingFeeRateStr);
    }

    /**
     * 同步订单信息成功后查询任务信息，并锁定
     *
     * @param order order
     * @return {@link }
     * @since 2022/12/2 14:26
     */
    public StrategyTaskContract getTaskWhenSyncOrder(StrategyOrderContract order) {
        StrategyTaskContract task = strategyTaskContractMapper.selectWhenSyncOrder(order.getTaskId());
        if (Objects.isNull(task)) {
            String errorMsg = String.format("未能查询到任务信息，id：%s", order.getTaskId());
            throw new MqAckLogException(errorMsg);
        } else if (!(Objects.equals(StrategyTaskContractEnum.Status.PAUSE.getValue(), task.getStatus())
                || Objects.equals(StrategyTaskContractEnum.Status.RUNNING.getValue(), task.getStatus())
                || Objects.equals(StrategyTaskContractEnum.Status.ERROR.getValue(), task.getStatus()))) {
            // 加仓需要任务状态为暂停、运行中，平仓可以额外允许一个异常状态
            String errorMsg = String.format("任务异常，状态为：%s", StrategyTaskContractEnum.Status.findName(task.getStatus()));
            throw new MqAckLogException(errorMsg);
        }

        return task;
    }

    /**
     * 根据最新任务数据刷新任务缓存
     *
     * @param cache        任务缓存
     * @param task         任务
     * @param lastDealAvg  上次成交均价
     * @param positionList 仓位信息
     * @return {@link StrategyTaskContractCacheBO}
     * @since 2022/12/2 14:52
     */
    public StrategyTaskContractCacheBO flushTaskCacheByEntity(StrategyTaskContractCacheBO cache,
                                                              StrategyTaskContract task,
                                                              @Nullable BigDecimal lastDealAvg,
                                                              @Nullable List<StrategyPositionContract> positionList) {

        StrategyTaskContractCacheBO newCache = StrategyTaskContractStruct.INSTANCE.entity2CacheBO(task);
        newCache.setHighestPrice(cache.getHighestPrice());
        newCache.setLowestPrice(cache.getLowestPrice());
        newCache.setHighestProfit(cache.getHighestProfit());
        newCache.setHighestProfitRate(cache.getHighestProfitRate());
        newCache.setHighestFluctuationRatio(cache.getHighestFluctuationRatio());
        newCache.setForwardStopLowestPrice(cache.getForwardStopLowestPrice());
        newCache.setForwardStopHighestPrice(cache.getForwardStopHighestPrice());
        // 止盈止损委托
        newCache.setStopOrderList(cache.getStopOrderList());

        // 有仓位信息时，更新仓位信息
        newCache.setPositionList(CollectionUtils.isEmpty(positionList) ? cache.getPositionList() : positionList);
        // 有成交均价时，刷新成交均价
        newCache.setLastOpenAvg(Objects.isNull(lastDealAvg) ? cache.getLastOpenAvg() : lastDealAvg);

        redisUtils.flushPersistentCache(RedisKeyEnum.STRATEGY_TASK_CONTRACT, task.getId(), newCache);

        return newCache;
    }

    /**
     * 输出错误日志
     *
     * @param method 方法
     * @param e      异常
     * @param args   请求参数
     * @since 2022/12/3 14:43
     */
    public void logApiErrorMsg(String method, Throwable e, Object... args) {
        log.error("{} 发生异常：{} \n参数：\n{}",
                method,
                e.getMessage(),
                commonUtil.toJson(args));
    }

    /**
     * 根据币种实时价格将USDT手续费转换为UNT
     *
     * @param usdt USDT手续费
     * @return {@link BigDecimal}
     * @since 2022/12/3 15:05
     */
    public BigDecimal transferUSDT2UNT(BigDecimal usdt) {

        if (usdt.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }

        BigDecimal untPrice = redisUtils.getCurrencyPrice(WalletConstant.UNT);
        return usdt.divide(untPrice, SystemConfigConstants.amountScale, SystemConfigConstants.mainCurRoundingMode);

    }

    /**
     * 扣减燃油费
     *
     * @param userId 用户id
     * @param type   类型（0服务费、1盈利结算）
     * @param amount 金额
     * @return {@link BigDecimal}
     * @since 2022/12/3 15:40
     */
    public BigDecimal updateFuel(Long userId,
                                 FuelConsumeEnum.Type type,
                                 BigDecimal amount) {

        UpdateFuelBalanceDTO dto = new UpdateFuelBalanceDTO();
        dto.setUserId(userId);
        dto.setType(type);
        dto.setQuantity(amount);
        dto.setIsAdd(false);
        dto.setCurrency(WalletConstant.USDT);

        // 修改后的新余额
        return fuelAccountFeign.updateBalance(dto, SecurityConstants.FROM_IN);
    }

    /**
     * 根据user获取欧易ApiKey配置
     *
     * @param userId     用户id
     * @param platformId 平台id
     * @return {@link APIConfiguration}
     * @since 2022/11/30 17:12
     */
    public APIConfiguration getOkxApiConfig(Long userId, Long platformId) throws ApiRequestException {

        // 获取平台信息
        SystemPlatform platform = redisUtils.getPersistentCache(RedisKeyEnum.PLATFORM, platformId, SystemPlatform.class);
        if (Objects.isNull(platform)) {
            throw new ApiRequestException("平台信息已不可用");
        }

        return getOkxApiConfig(userId, platform);

    }

    /**
     * 获取欧易ApiKey配置，只包含地址
     *
     * @param platformId 平台id
     * @return {@link APIConfiguration}
     * @since 2022/11/30 17:12
     */
    public APIConfiguration getOkxApiConfig(Long platformId) throws ApiRequestException {

        // 获取平台信息
        SystemPlatform platform = redisUtils.getPersistentCache(RedisKeyEnum.PLATFORM, platformId, SystemPlatform.class);
        if (Objects.isNull(platform)) {
            throw new ApiRequestException("平台信息已不可用");
        }

        APIConfiguration apiConfig = new APIConfiguration();
        apiConfig.setEndpoint(platform.getUrl());

        return apiConfig;

    }

    /**
     * 根据user获取欧易ApiKey配置
     *
     * @param userId   用户id
     * @param platform 平台
     * @return {@link APIConfiguration}
     * @since 2022/11/30 17:12
     */
    public APIConfiguration getOkxApiConfig(Long userId, SystemPlatform platform) throws ApiRequestException {

        // 获取用户apiKey信息
        UserApi userApi = redisUtils.getPersistentCache(RedisKeyEnum.USER_API, userId + "_" + platform.getId(), UserApi.class);
        apiOkxUtil.checkUserApi(userApi);

        // APIConfiguration apiConfig = new APIConfiguration();
        // apiConfig.setApiKey("09450d54-9c0b-4ca7-a971-319829ccd05c");
        // apiConfig.setSecretKey("7CB17BC4B37F79782D3DB915BB67E44C");
        // apiConfig.setPassphrase("/Ziyou123456");
        // apiConfig.setEndpoint("https://www.okx.com");

        APIConfiguration apiConfig = new APIConfiguration();
        apiConfig.setApiKey(userApi.getApiKey());
        apiConfig.setSecretKey(userApi.getSecretKey());
        apiConfig.setPassphrase(userApi.getPassword());
        apiConfig.setEndpoint(platform.getUrl());

        return apiConfig;

    }

    /**
     * 查询仓位，并锁定
     *
     * @param taskId 任务id
     * @return {@link List< StrategyPositionContract>}
     * @since 2022/12/7 20:38
     */
    public List<StrategyPositionContract> getPositionList(Long taskId) {
        // 查询两个仓位信息，并锁定
        QueryWrapper<StrategyPositionContract> queryPositionList = new QueryWrapper<>();
        queryPositionList.lambda()
                .eq(StrategyPositionContract::getTaskId, taskId)
                .last("for update");
        return strategyPositionContractMapper.selectList(queryPositionList);
    }

    /**
     * 根据方向获取当前仓位
     *
     * @param list     当前仓位列表
     * @param side     方向
     * @param nullable 是否可以为空
     * @return {@link StrategyPositionContract}
     * @since 2022/12/6 16:33
     */
    public static StrategyPositionContract getCurrentPositionBySide(List<StrategyPositionContract> list,
                                                                    String side,
                                                                    boolean nullable) {
        Optional<StrategyPositionContract> first = list.stream()
                .filter(i -> Objects.equals(i.getSide(), side)
                        && (Objects.equals(i.getStatus(), StrategyPositionContractEnum.Status.EMPTY.getValue())
                        || Objects.equals(i.getStatus(), StrategyPositionContractEnum.Status.HOLDING.getValue())
                        || Objects.equals(i.getStatus(), StrategyPositionContractEnum.Status.ADDING.getValue())
                        || Objects.equals(i.getStatus(), StrategyPositionContractEnum.Status.CLOSING.getValue())))
                .findFirst();

        if (first.isPresent()) {
            return first.get();
        } else if (nullable) {
            return null;
        } else {
            throw new MqNotAckException("数据异常，未能获取到方向对应的仓位");
        }
    }

    /**
     * 判断任务是否完成
     *
     * @param bo bo
     * @return {@link boolean}
     * @since 2022/12/6 21:09
     */
    public static boolean isTaskComplete(SyncOrderBO bo) {
        StrategyTaskContract task = bo.getTask();
        List<StrategyPositionContract> positionList = bo.getPositionList();

        // 委托中订单数量 != 0
        if (task.getPendingOrderNum() != 0) {
            return false;
        }

        // 没有订单时，如果任务异常，不管仓位是否已经全平，直接返回true
        if (bo.isTaskError()) {
            return true;
        }

        // 开仓计数
        int openCount = 0;
        // 平仓计数
        int closeCount = 0;

        // 遍历
        for (StrategyPositionContract i : positionList) {
            // 开过仓
            if (i.getNumOpen() > 0) {
                ++openCount;
            }

            // 已经平仓
            if (Objects.equals(StrategyPositionContractEnum.Status.CLOSE.getValue(), i.getStatus())) {
                ++closeCount;
            }
        }

        // 没有订单、没任务状态正常、全部平仓
        return openCount > 0 && openCount == closeCount;
    }

    /**
     * 获取燃油费余额
     *
     * @param userId 用户id
     * @return {@link BigDecimal}
     * @since 2022/12/10 15:43
     */
    public BigDecimal getFuelBalance(Long userId) {
        R<BigDecimal> r = fuelAccountFeign.getBalance(userId, SecurityConstants.FROM_IN);
        if (!r.getIsSuccess() || Objects.isNull(r.getData())) {
            throw new DuckweedException("未能查询到燃油币账户信息");
        }

        return r.getData();
    }

    /**
     * 格式化币对名称
     * <p>ETHUSDT -> ETH/USDT
     *
     * @param symbolName 币对名称
     * @return {@link String}
     * @since 2022/12/11 12:47
     */
    public static String formatSymbolName(String symbolName) {
        return symbolName.replace("USDT", "/USDT");
    }

    /**
     * 格式化杠杆倍数
     * <p>20 -> 20X
     *
     * @param multiple 杠杆倍数
     * @return {@link String}
     * @since 2022/12/11 12:47
     */
    public static String formatMultiple(BigDecimal multiple) {
        return BigDecimalUtils.toStrRemoveExtraZero(multiple) + "X";
    }

    /**
     * 获得策略名称
     * <p>策略组名称-币对名称 方向 杠杆倍数
     *
     * @param group    策略组
     * @param strategy 策略
     * @return {@link String}
     * @since 2022/12/11 12:43
     */
    public static String getStrategyName(StrategyGroupContract group,
                                         StrategyContract strategy) {

        // 翻译字典项
        String side = StrategyContractEnum.Side.findName(strategy.getSide());
        // 格式化币对名称
        String symbolName = formatSymbolName(strategy.getSymbolName());
        // 格式化杠杆倍数
        String multipleStr = formatMultiple(strategy.getMultiple());

        return String.format("%s-%s %s %s", group.getName(), symbolName, side, multipleStr);

    }

    /**
     * 燃油费预警值比例
     *
     * @return {@link BigDecimal}
     * @since 2022/12/11 20:43
     */
    public BigDecimal getFuelWarningRatio() {
        String ratioStr = ParamResolver.getStr(SystemParamNameConstant.STRATEGY_FUEL_WARNING_RATIO, "20");
        return BigDecimal.valueOf(Double.parseDouble(ratioStr));
    }

    /**
     * 欧易BrokerCode
     *
     * @return {@link String}
     * @since 2022/12/11 20:43
     */
    public String getOKXBrokerCode() {
        return ParamResolver.getStr(SystemParamNameConstant.STRATEGY_OKX_BROKER_CODE, "");
    }

    /**
     * 删除同步次数累计
     *
     * @param id id
     * @since 2022/12/2 15:00
     */
    public void removeSyncCount(Long id) {
        redis.opsForHash().delete(RedisKeyEnum.STRATEGY_ORDER_CONTRACT_SYNC_COUNT.getIncrCacheKey(),
                id.toString());
    }

    /**
     * 根据id查询产品信息
     *
     * @param productId 产品id
     * @param nullable  是否可以为空
     * @return {@link SystemProduct}
     * @since 2023/1/5 11:38
     */
    public SystemProduct getProductById(Long productId, boolean nullable) {
        R<SystemProduct> productR = systemProductFeign.getById(productId, SecurityConstants.FROM_IN);
        if (!productR.getIsSuccess() && !nullable) {
            throw new DuckweedException("未能查询到产品信息");
        }
        SystemProduct product = productR.getData();
        if (Objects.isNull(product) && !nullable) {
            throw new DuckweedException("未能查询到产品信息");
        }

        return product;
    }

    /**
     * 判断是否是模拟盘
     *
     * @return {@link boolean}
     * @since 2023/1/25 20:18
     */
    public static boolean isSimulated() {
        String env = ParamResolver.getStr(SystemParamNameConstant.STRATEGY_CLIENT_ENVIRONMENT, "");
        return "simulated".equals(env);
    }

    /**
     * 获取币对最新价
     *
     * @param symbolName 币对名称
     * @return {@link BigDecimal}
     * @since 2022/11/28 19:31
     */
    public BigDecimal getPrice(String symbolName, String platform) {
        BigDecimal lastPrice = redisUtils.getSymbolPrice(symbolName, platform + CommonRedisConstants.LAST_PRICE);
        if (Objects.isNull(lastPrice)) {
            String errorMsg = String.format("币对价格为空：%s", symbolName);
            throw new MqAckLogException(errorMsg);
        }

        return lastPrice;
    }

    /**
     * 校验止盈止损委托参数
     *
     * @param stopOrder dto
     * @param price     price
     * @since 2022/9/6 21:01
     */
    public static void validStopOrder(StrategyStopOrderContractAddDTO stopOrder, BigDecimal price) {
        // 是否需要止盈
        boolean hasStopProfit = Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), stopOrder.getHasStopProfit());
        // 是否需要止损
        boolean hasStopLost = Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), stopOrder.getHasStopLost());

        if (!hasStopProfit && !hasStopLost) {
            throw new DuckweedException("参数异常");
        }

        // 是否是做多
        boolean sideIsLong = Objects.equals(StrategyOrderContractEnum.Side.LONG.getValue(), stopOrder.getSide());

        // 止盈
        if (hasStopProfit) {
            // 止盈触发价
            if (Objects.isNull(stopOrder.getStopProfitTrigger()) || stopOrder.getStopProfitTrigger().compareTo(BigDecimal.ZERO) <= 0) {
                throw new DuckweedException("请输入正确的止盈触发价格");
            }

            // 做多
            if (sideIsLong) {
                // 比较触发价
                if (stopOrder.getStopProfitTrigger().compareTo(price) <= 0) {
                    throw new DuckweedException("止盈触发价格需要高于标记价格");
                }

            } else {
                // 做空
                // 比较触发价
                if (stopOrder.getStopProfitTrigger().compareTo(price) >= 0) {
                    throw new DuckweedException("止盈触发价格需要低于标记价格");
                }
            }


            // 止盈价格类型
            if (Strings.isEmpty(stopOrder.getStopProfitType())) {
                throw new DuckweedException("数据异常，stopProfitType为空");
            }
            // 是否是限价止盈
            boolean isLimitStopProfit = Objects.equals(StrategyStopOrderContractEnum.StopType.LIMIT.getValue(), stopOrder.getStopProfitType());
            // 限价止盈，市价不用管
            if (isLimitStopProfit) {
                if (Objects.isNull(stopOrder.getStopProfitPrice()) || stopOrder.getStopProfitPrice().compareTo(BigDecimal.ZERO) <= 0) {
                    throw new DuckweedException("请输入正确的止盈价格");
                }
            }
        }

        // 止损
        if (hasStopLost) {
            // 止损触发价
            if (Objects.isNull(stopOrder.getStopLostTrigger()) || stopOrder.getStopLostTrigger().compareTo(BigDecimal.ZERO) <= 0) {
                throw new DuckweedException("请输入正确的止损触发价格");
            }

            // 做多
            if (sideIsLong) {
                // 比较触发价
                if (stopOrder.getStopLostTrigger().compareTo(price) >= 0) {
                    throw new DuckweedException("止损触发价格需要低于标记价格");
                }

            } else {
                // 做空
                // 比较触发价
                if (stopOrder.getStopLostTrigger().compareTo(price) <= 0) {
                    throw new DuckweedException("止损触发价格需要高于标记价格");
                }
            }

            // 止损价格类型
            if (Strings.isEmpty(stopOrder.getStopLostType())) {
                throw new DuckweedException("数据异常，stopLostType为空");
            }
            // 是否是限价止损
            boolean isLimitStopLost = Objects.equals(StrategyStopOrderContractEnum.StopType.LIMIT.getValue(), stopOrder.getStopLostType());
            // 限价止损，市价不用管
            if (isLimitStopLost) {
                if (Objects.isNull(stopOrder.getStopLostPrice()) || stopOrder.getStopLostPrice().compareTo(BigDecimal.ZERO) <= 0) {
                    throw new DuckweedException("请输入正确的止损价格");
                }
            }
        }

        // 同时具有止盈止损
        if (hasStopLost && hasStopProfit) {
            // 做多
            if (sideIsLong) {
                // 触发价格判断
                if (stopOrder.getStopProfitTrigger().compareTo(stopOrder.getStopLostTrigger()) <= 0) {
                    throw new DuckweedException("止盈触发价格需要高于止损触发价格");
                }

            } else {
                // 做空，触发价格判断
                if (stopOrder.getStopProfitTrigger().compareTo(stopOrder.getStopLostTrigger()) >= 0) {
                    throw new DuckweedException("止盈触发价格需要低于止损触发价格\n");
                }
            }
        }
    }
}
